﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cloo;
using System.Diagnostics.Contracts;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;

namespace SimpleCLML.LogisticRegression
{
    /// <summary>
    /// Logistic regression model.
    /// Use LogisticRegressionTrainer to train one.
    /// </summary>
    [Serializable]    
    public class LogisticRegression: ISerializable
    {        
        /// <summary>
        /// θ vector
        /// </summary>
        private float[] theta;

        public float[] Theta { get { return (float[])theta.Clone(); } }
        
        // for deserialization        
        private LogisticRegression() { }

        /// <summary>
        /// Create logistic regression model using θ vector
        /// </summary>
        /// <param name="theta">θ vector</param>
        public LogisticRegression(float[] theta)
        {
            this.theta = theta;
        }

        /// <summary>
        /// Set new θ vector.
        /// </summary>
        /// <param name="newTheta">New θ vector</param>
        public void SetTheta(float[] newTheta)
        {
            Contract.Requires(newTheta != null);
            Contract.Requires(theta == null || newTheta.Length == theta.Length);
            if (theta == null)
                theta = new float[newTheta.Length];
            Array.Copy(newTheta, theta, theta.Length);
        }

        /// <summary>
        /// Cost function
        /// </summary>
        /// <param name="examples">Examples</param>
        /// <returns>model error</returns>
        public double Cost(IEnumerable<TrainingExample> examples)
        {
            int i = 0;
            double sum = 0.0;
            foreach (var example in examples)
            {
                var tmp = Math.Log(example.y ? Predict(example.x) : 1.0 - Predict(example.x));
                //if (i < 4) Console.WriteLine("right[{0}] = {1}", i, tmp);
                sum += tmp;
                i++;
            }
            return -sum / i;
        }

        /// <summary>
        /// Predict class using current θ
        /// </summary>
        /// <param name="x">Feature vector (MUST be one element shorter than θ)</param>
        /// <returns>Predicted class</returns>
        public float Predict(float[] x)
        {
            Contract.Requires(x.Length == theta.Length - 1);

            float[] x_with0 = new float[x.Length + 1];
            x_with0[0] = 1.0f;
            Array.Copy(x, 0, x_with0, 1, x.Length);

            float sum = 0.0f;
            for (int i = 0; i < theta.Length; i++)
                sum += x_with0[i] * theta[i];

            return (float)(1.0 / (1.0 + Math.Exp(-sum)));
        }

        #region Serialization

        /// <summary>
        /// Saves model to a file
        /// </summary>
        /// <param name="filename">File name</param>
        public void Save(string filename)
        {
            using (var stream = File.Create(filename))
            {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(stream, this);
            }
        }

        /// <summary>
        /// Loads model from a file
        /// </summary>
        /// <param name="filename">File name</param>
        /// <returns>Deserialized model</returns>
        public static LogisticRegression Load(string filename)
        {
            using (var stream = File.OpenRead(filename))
            {
                BinaryFormatter bf = new BinaryFormatter();
                return (LogisticRegression)bf.Deserialize(stream);
            }
        }

        /// <summary>
        /// Makes serialization version independent
        /// </summary>        
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (info == null) 
                throw new System.ArgumentNullException("info");
            info.AddValue("Theta", theta);
        }
        #endregion Serialization
    }
}
